
#include <sys/stat.h>
#include <sys/types.h>

#include <iostream>
#include <map>

#include <sys/inotify.h>
#include <string.h>

#include <boost/bind.hpp>

#include "s_server.h"
#include "s_srp_database.h"
#include "s_connection.h"

#include "dhd_fs.pb.h"

namespace dhd
{
namespace server
{


   Server::Server( const ServerConfig & _conf ) :
       ssl_ctx(0), acceptor(0), conf(_conf), srp_db(0)
   {
       if ( conf.use_ssl )
       {
           std::cout << "SSL Enabled" << std::endl;
           
           ssl_ctx = new boost::asio::ssl::context(io_service, boost::asio::ssl::context::tlsv1_server);
           
           DH *dh = DH_new();
           if (!dh) throw InitializationError( "Failed to create DH parameter" );
           if (!DH_generate_parameters_ex(dh, 2, DH_GENERATOR_2, 0) ) throw InitializationError( "Failed generate DH parameters" );
           int codes;
           if (!DH_check(dh, &codes)) throw InitializationError( "DH Parameter Check Failed" );
           SSL_CTX_set_tmp_dh(ssl_ctx->impl(), dh);
           if ( SSL_CTX_set_cipher_list( ssl_ctx->impl(), "ADH-AES256-SHA" ) != 1 ) throw InitializationError( "Failed to set SSL cipher to Anonymous AES256" );
       }
       
       if ( conf.use_srp )
       {
           std::cout << "SRP Enabled" << std::endl;
           
           srp_db = new SRPDatabase( conf.srp_database_file );
           
           srp_init(0,0);
       }
       
       acceptor = new tcp::acceptor(io_service, tcp::endpoint(tcp::v4(), conf.serv_port));
       
       inotify_thread = boost::thread( boost::bind( &Server::inotify_thread_main, this ) );
   }
    


   Server::~Server()
   {
       delete ssl_ctx;
       delete acceptor;
       
       if ( srp_db )
       {
           delete srp_db;
           srp_fini();
       }
   }


   void Server::on_accept( tcp::socket * sock, tcp::endpoint * endpoint, const boost::system::error_code& error )
   {
       std::cerr << "*** Async Connection Opened! ***" << std::endl;
       
       if (error)
       {
           std::cerr << "Error accepting connection: " << error << std::endl;
           delete sock;
           delete endpoint;
       }
       else if ( this->ssl_ctx )
       {
           ssl_socket * ssl_sock = new ssl_socket( *sock, *this->ssl_ctx );
           
           ssl_sock->async_handshake(boost::asio::ssl::stream_base::server, 
                                     boost::bind(&Server::on_ssl_connect, 
                                                 this, 
                                                 sock,
                                                 endpoint,
                                                 ssl_sock,
                                                 boost::asio::placeholders::error));
       }
       else
       {
           conn_map[ *endpoint ] = new Connection( endpoint, sock, 0, *this );
       }
    
       async_accept_connection();
   }


   void Server::on_ssl_connect( tcp::socket *sock, tcp::endpoint *endpoint, ssl_socket * ssl_sock, const boost::system::error_code & error )
   {
       if (error)
       {
           std::cerr << "Error starting SSL session: " << error << std::endl;
           sock->close();
           delete sock;
           delete endpoint;
           delete ssl_sock;
       }
       else
       {
           conn_map[ *endpoint ] = new Connection( endpoint, sock, ssl_sock, *this );
       }
   }



   void Server::async_accept_connection()
   {
       tcp::socket   *sock     = new tcp::socket(io_service);
       tcp::endpoint *endpoint = new tcp::endpoint;
       
       std::cerr << "*** Async Waiting for Connection ***" << std::endl;
       
       acceptor->async_accept( *sock, *endpoint, boost::bind( &Server::on_accept, this, sock, endpoint, boost::asio::placeholders::error ) );
   }


   void Server::connection_closed( Connection * conn )
   {
       std::cerr << "*** Backend Connection Closed " << std::endl;
       
       ConnMap::iterator i = conn_map.find( conn->get_endpoint() );
       
       if ( i != conn_map.end() )
       {
           conn_map.erase(i);
       }
       else
       {
           std::cerr << "**** CONNECTION NOT FOUND IN MAP... SHOULD NEVER SEE THIS!\n";
           assert(0);
       }
   }


   void Server::inotify_thread_main()
   {
       dhd::ToClient to_cli;
    
       int fd = inotify_init();
       
       if (fd<0) {
           std::cerr << "Inotify failed to initialize" << std::endl;
           return;
       }
       
       int wd = inotify_add_watch(fd, conf.serv_dir.c_str(), IN_CREATE | IN_DELETE | IN_ATTRIB);
    
       if (wd < 0) {
           std::cerr << "Inotify Watch failed to initialize" << std::endl;
           return;
       }
       
       const int BUFF_SIZE = (sizeof(struct inotify_event) + 16) * 100; // fairly arbitrary guess at a good buffer size
       
       char                  buff[ BUFF_SIZE ];
       char                 *rbegin   = buff;
       const char * const    buff_end = buff + BUFF_SIZE;
       struct inotify_event *event = 0;
    
       int   nread = 0;
       char *p     = 0;
       char *pend  = 0;
       
       dhd::INotifyEvent * ine = 0;
    
       char   full_path[4096];
       char * pcpy = &full_path[ conf.serv_dir.length() + 1 ];
       
       strcpy(full_path, conf.serv_dir.c_str());
       pcpy[ -1 ] = '/';
       
       const int MAX_NAME_LEN = sizeof(full_path) - (conf.serv_dir.length() + 1);
       
       struct stat stbuf;
       
       bool send_message = false;
    
       for(;;)
       {
           send_message = false;
        
           to_cli.Clear();
           to_cli.set_msg_type( dhd::DHD_INOTIFY_EVENT );
           to_cli.set_result(0);
            
           nread = read(fd, rbegin, buff_end - rbegin);
                
           if (nread < 0)
           {
               if (errno == EINTR)
                   continue;
               else {
                   std::cerr << "Unexpected error encountered while reading inotify descriptor" << std::endl;
                   return;
               }
           }
           
           p     = buff;
           pend  = rbegin + nread;
           event = (struct inotify_event *) p;
           
           ine   = 0;
        
        
           while ( p + sizeof(struct inotify_event) <= pend && p + sizeof(struct inotify_event) + event->len <= pend )
           {
               if ( event->len && event->len < MAX_NAME_LEN )
               {
                   strcpy(pcpy, event->name);
                   
                   if ( event->mask & IN_ATTRIB || event->mask & IN_CREATE )
                   {                
                       if (lstat(full_path, &stbuf) != -1)
                       {
                           ine = to_cli.add_inotify();
                           
                           ine->set_name( event->name );
                           
                           if ( event->mask & IN_CREATE ) 
                               ine->set_event_type( dhd::INOTIFY_CREATE );
                           else
                               ine->set_event_type( dhd::INOTIFY_ATTRIB );
                           
                           ine->set_mode    ( stbuf.st_mode );
                           ine->set_uid     ( stbuf.st_uid  );
                           ine->set_gid     ( stbuf.st_gid  );
                           ine->set_inode   ( stbuf.st_ino  );
                           
                           send_message = true;
                       }
                   }
                   else if ( event->mask & IN_DELETE )
                   {
                       ine = to_cli.add_inotify();
                       
                       ine->set_event_type( dhd::INOTIFY_DELETE );
                       ine->set_name( event->name );
                       
                       send_message = true;
                   }
               }
               
               
               p     += sizeof(struct inotify_event) + event->len;
               event  = (struct inotify_event *) p;
           }
        
        
           if (send_message)
           {
               PBuffer pbuff = allocate_buffer( to_cli.ByteSize() );
               
               to_cli.SerializeToArray( pbuff->data(), pbuff->size() );
               
               for (ConnMap::iterator i = conn_map.begin(); i != conn_map.end(); i++)
                   i->second->send_broadcast( pbuff, 0 );
           }
           
           // If there's a partial message in the buffer, move it to the front of the buffer and set the next
           // read to fill in where it leaves off
           if ( p != pend )
           {
               if ( p != buff ) 
               {
                   memmove(buff, p, pend - p);
                   rbegin = buff + (pend - p);
               } 
               else
                   rbegin = pend;
           }
           else
               rbegin = buff;
       }
   }



   void Server::main()
   {
       async_accept_connection();
       
       io_service.run();
   }
   
   
}//end namespace dhd::server
}//end namespace dhd

   